#include "fiber.h"

#include <stdint.h>
#include <atomic>
#include <iostream>

namespace learnco {

static std::atomic<uint64_t> s_fiber_id {0};
static std::atomic<uint64_t> s_fiber_count {0};

//记录当前线程正在执行的fiber
static thread_local Fiber* t_fiber = nullptr;
static thread_local Fiber::ptr t_threadFiber = nullptr;


Fiber::Fiber()
    :m_id(++s_fiber_id){
    m_state = EXEC;
    SetThis(this);
    getcontext(&m_ctx);
    ++s_fiber_count;
}

Fiber::Fiber(std::function<void()> cb, size_t stacksize)
    :m_id(++s_fiber_id)
    ,m_cb(cb)
    ,m_stacksize(stacksize) {

    //使用线程之前必须先调用该函数
    //创建线程的(上下文现场暂存)协程, t_threadfiber
    //fiber->swapin:  将当前执行函数的上下文暂存在t_threadfiber(保存现场),再切换到fiber的上下文
    //fiber->swapout: 回到t_threadfiber的上下文(恢复现场)
    //learnco::Fiber::GetThis();
    if(!t_threadFiber){
            //第一次创建主协程，中转，跳转的时候将当前位置保存在该协程
        Fiber::ptr main_fiber(new Fiber);
        t_threadFiber = main_fiber;
    }

    //alloc memory
    m_stack = malloc(m_stacksize);

    getcontext(&m_ctx);
    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;
    //设置fiber的入口执行函数伪mainFunc()
    makecontext(&m_ctx, &Fiber::MainFunc, 0);

    ++s_fiber_count;
}

Fiber::~Fiber() {
    --s_fiber_count;
    if(m_stack)
    {
        free(m_stack);
    }

}

//切换到当前协程执行
void Fiber::swapIn() {
    SetThis(this);
    m_state = EXEC;
    //当前上下文保存到arg1, 跳转到arg2
    swapcontext(&t_threadFiber->m_ctx, &m_ctx);
}

void Fiber::swapIn(ucontext_t &ctx){
    SetThis(this);
    m_state = EXEC;
    m_recvctx = &ctx;
    swapcontext(&ctx, &m_ctx);
}

//切换到后台
void Fiber::swapOut() {
    SetThis(t_threadFiber.get());
    swapcontext(&m_ctx, &t_threadFiber->m_ctx);
}

//切换到后台
void Fiber::swapOut(ucontext_t* ctx) {
    swapcontext(&m_ctx, ctx);
}


//设置当前协程
void Fiber::SetThis(Fiber* f) {
    t_fiber = f;
}

//返回当前协程
Fiber::ptr Fiber::GetThis() {
    if(t_fiber) {
        return t_fiber->shared_from_this();
    }

    //协程作为执行的最小单位，将某些线程作协程看待
    Fiber::ptr main_fiber(new Fiber);
    t_threadFiber = main_fiber;
    return main_fiber->shared_from_this();
}


//重置协程函数，并重置状态
//INIT，TERM
void Fiber::reset(std::function<void()> cb) {
    m_cb = cb;
    getcontext(&m_ctx);

    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;

    makecontext(&m_ctx, &Fiber::MainFunc, 0);
    m_state = INIT;
}

void Fiber::MainFunc() {
    Fiber::ptr cur = GetThis();

    try{
        cur->m_cb();
        cur->m_cb = nullptr;
        cur->m_state = TERM;
    }catch (...) {
        cur->m_state = EXCEPT;
    }

    auto temp = cur.get();
    cur.reset();
    temp->m_recvctx == nullptr ? temp->swapOut() : temp->swapOut(temp->m_recvctx);
}


void Fiber::YieldToHold() {
    Fiber::ptr cur = GetThis();
    cur->m_state = HOLD;
    cur->swapOut();
}

void Fiber::YieldToReady() {
    Fiber::ptr cur = GetThis();
    cur->m_state = READY;
    cur->swapOut();
}

uint64_t GetFiberID(){
    if(t_fiber)
    {
        return t_fiber->m_id;
    }
    return 0;
}

//总协程数
uint64_t Fiber::TotalFibers() {
    return s_fiber_count;
}
    
}